use core::f32;
use std::{collections::HashSet, time::Instant};
use cgmath::{InnerSpace, Vector3, Zero};
use winit::{keyboard::KeyCode, event::ElementState};
use getset::{CloneGetters, CopyGetters, Getters, MutGetters, Setters, WithSetters};
use std::sync::LazyLock;
use crate::engine::{njm_game_obj::NjmGameObj, njm_keyboard_key::*};

pub static KEY_SET_MOVEMENT: LazyLock<HashSet<KeyCode>> = LazyLock::new(|| [
    KeyCode::KeyA, KeyCode::KeyD, KeyCode::KeyS, KeyCode::KeyW, KeyCode::KeyQ, KeyCode::KeyE,
    KeyCode::ArrowLeft, KeyCode::ArrowRight, KeyCode::ArrowUp, KeyCode::ArrowDown,
].iter().cloned().collect());

// move ctrler specific
#[derive(Default, Getters, Setters, WithSetters, MutGetters, CopyGetters, CloneGetters)]
pub struct KeyboardMovementController {
    #[getset(get = "pub", set = "pub")]
    keys: KeyMappings,

    #[getset(get = "pub", set = "pub")]
    states: KeyStates,

    #[getset(get = "pub", set = "pub")]
    move_speed: f32,

    #[getset(get = "pub", set = "pub")]
    look_speed: f32,
}

impl KeyboardMovementController {
    pub const DEFAULT_MOVE_SPPED: f32 = 1.0;
    pub const DEFAULT_LOOK_SPPED: f32 = 1.0;

    pub fn update_key_states(&mut self, keycode: KeyCode, state: ElementState) {
        if KEY_SET_MOVEMENT.contains(&keycode) {
            let key_state_raw: KeyStateRaw = state.into();
            let key_state: KeyState = key_state_raw.into();
            let states = &mut self.states;
            // 
            if keycode == *self.keys.move_left() {
                states.set_move_left_state(key_state);
                return;
            }
            if keycode == *self.keys.move_right() {
                states.set_move_right_state(key_state);
                return;
            }
            if keycode == *self.keys.move_forward() {
                states.set_move_forward_state(key_state);
                return;
            }
            if keycode == *self.keys.move_backward() {
                states.set_move_backward_state(key_state);
                return;
            }
            if keycode == *self.keys.move_up() {
                states.set_move_up_state(key_state);
                return;
            }
            if keycode == *self.keys.move_down() {
                states.set_move_down_state(key_state);
                return;
            }
            // 
            if keycode == *self.keys.look_left() {
                states.set_look_left_state(key_state);
                return;
            }
            if keycode == *self.keys.look_right() {
                states.set_look_right_state(key_state);
                return;
            }
            if keycode == *self.keys.look_down() {
                states.set_look_down_state(key_state);
                return;
            }
            if keycode == *self.keys.look_up() {
                states.set_look_up_state(key_state);
                return;
            }            
        }
    }

    pub fn move_in_plane_xz(&self, dt: f32, obj: &mut NjmGameObj) {
        let mut rotate: cgmath::Vector3<f32> = Vector3::zero();
        if self.states.look_right_state().is_pressed() {rotate.y += 1.0}
        if self.states.look_left_state().is_pressed() {rotate.y -= 1.0}
        if self.states.look_up_state().is_pressed() {rotate.x += 1.0}
        if self.states.look_down_state().is_pressed() {rotate.x += 1.0}
        // if there has a rotation
        if rotate.dot(rotate) > f32::EPSILON {
            obj.transform.rotation += self.look_speed * dt * rotate.normalize();
        }
        // limit pitch values between about +/- 85 degrees
        obj.transform.rotation.x = obj.transform.rotation.x.clamp(-1.5, 1.5);
        // limit yaw values between 0 and 360 degrees
        obj.transform.rotation.y = obj.transform.rotation.y % (f32::consts::PI * 2.0);

        let yaw = obj.transform.rotation.y;
        let forward: Vector3<f32> = [yaw.sin(), 0.0, yaw.cos()].into();
        let right: Vector3<f32> = [forward.z, 0.0, -forward.x].into();
        let up: Vector3<f32> = [0.0, -1.0, 0.0].into();

        let mut move_dir: Vector3<f32> = Vector3::zero();
        if self.states.move_forward_state().is_pressed() {move_dir += forward}
        if self.states.move_backward_state().is_pressed() {move_dir -= forward}
        if self.states.move_right_state().is_pressed() {move_dir += right}
        if self.states.move_left_state().is_pressed() {move_dir -= right}
        if self.states.move_up_state().is_pressed() {move_dir += up}
        if self.states.move_down_state().is_pressed() {move_dir -= up}
        
        if move_dir.dot(move_dir) > f32::EPSILON {
            obj.transform.translation += self.move_speed * dt * move_dir.normalize();
        }
    }
}
